#!/usr/bin/env python

import pygtk
pygtk.require('2.0')
import gtk, gobject, cairo
import gtk.gtkgl
import sys, os
import re
import exceptions
from traceback import *

from OpenGL.GL import *
from OpenGL.GLU import *
from gtk.gtkgl.apputils import *
import gtk.gtkgl.apputils

class OglEntity(gtk.gtkgl.apputils.GLScene,
             gtk.gtkgl.apputils.GLSceneButton,
             gtk.gtkgl.apputils.GLSceneButtonMotion):

    def __init__(self):
        gtk.gtkgl.apputils.GLScene.__init__(self,
                         gtk.gdkgl.MODE_RGB   |
                         gtk.gdkgl.MODE_DEPTH |
                         gtk.gdkgl.MODE_DOUBLE)
    
    def init(self):
        pass
    
    def reshape(self, width, height):
        pass

    def display(self, width, height):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        pass
        
    def button_press(self, width, height, event):
        pass
    
    def button_release(self, width, height, event):
        pass
    
    def button_motion(self, width, height, event):
        pass

DEF_CODE = \
"""
class Entity(OglEntity):
    def __init__(self):
        OglEntity.__init__(self)
"""
exec( DEF_CODE )

def get_error_line( exec_info ):
    tb = exec_info[2]
    print_tb(tb)
    tbList = extract_tb(tb)
    lastCall = tbList[len(tbList)-1]
    lineNo = lastCall[1]    
    return lineNo

class EntityDecorator(OglEntity):
    
    def __init__(self, decorated, view):
        OglEntity.__init__(self)
        self.decorated = decorated;
        self.view = view
    
    def init(self):
        try:
            self.decorated.init()
        except Exception, inst:
            line = get_error_line( sys.exc_info() )
            self.view.runtime_error( line, 1, inst )
    
    def reshape(self, width, height):
        try:
            self.decorated.reshape(width, height)
        except Exception, inst:
            line = get_error_line( sys.exc_info())
            self.view.runtime_error( line, 1, inst )

    def display(self, width, height):
        try:
            self.decorated.display(width, height)
        except Exception, inst:
            line = get_error_line( sys.exc_info())
            self.view.runtime_error( line, 1, inst )
        
    def button_press(self, width, height, event):
        try:
            self.decorated.button_press(width, height, event)
        except Exception, inst:
            line = get_error_line( sys.exc_info())
            self.view.runtime_error( line, 1, inst )
    
    def button_release(self, width, height, event):
        try:
            self.decorated.button_release(width, height, event)
        except Exception, inst:
            line = get_error_line( sys.exc_info())
            self.view.runtime_error( line, 1, inst )
    
    def button_motion(self, width, height, event):
        try:
            self.decorated.button_motion(width, height, event)
        except Exception, inst:
            line = get_error_line( sys.exc_info())
            self.view.runtime_error( line, 1, inst )

def exec_code( _new_code ):
    exec( _new_code )


class MainWindow:

    def build_actions(self):
        # Create an accelerator group
        accelgroup = gtk.AccelGroup()
        # Add the accelerator group to the toplevel window
        # window.add_accel_group(accelgroup) # do  in caller
        # Create an ActionGroup named ActionExample
        actiongroup = gtk.ActionGroup('ActionExample')
        
        # Create an action for quitting the program using a stock item
        refreshAction = gtk.Action('refresh_action', '_Refresh', 'View refresh', None )
        refreshAction.set_property('short-label', '_Refresh')
        # Connect a callback to the action
        refreshAction.connect('activate', self.do_refresh)
        # Add the action to the actiongroup with an accelerator
        actiongroup.add_action_with_accel( refreshAction, 'F5' )
        # Have the action use accelgroup
        refreshAction.set_accel_group(accelgroup)
        # Connect the accelerator to the action
        refreshAction.connect_accelerator()        

        saveAction = gtk.Action("save_action", "_Save", "Save script", gtk.STOCK_SAVE)
        saveAction.connect ("activate", self.save )
        actiongroup.add_action_with_accel( saveAction, None)
        saveAction.set_accel_group(accelgroup)

        openAction = gtk.Action("open_action", "_Open", "Load script", gtk.STOCK_OPEN)
        openAction.connect ("activate", self.open )
        actiongroup.add_action_with_accel( openAction, None)
        openAction.set_accel_group(accelgroup)

        newAction = gtk.Action("new_action", "_New", "New script", gtk.STOCK_NEW)
        newAction.connect ("activate", self.new )
        actiongroup.add_action_with_accel( newAction, None)
        newAction.set_accel_group(accelgroup)

        aboutAction = gtk.Action("about_action", "_About...", "About", None )
        aboutAction.connect ("activate", self.about )
        actiongroup.add_action_with_accel( aboutAction, None)
        aboutAction.set_accel_group(accelgroup)

        return ( accelgroup, actiongroup )

    def about(self, widget):
        about = gtk.Dialog("About PyPreview", self.window, gtk.DIALOG_MODAL, (gtk.STOCK_OK, gtk.RESPONSE_OK))
        about.set_default_response(gtk.RESPONSE_OK)
        
        vbox = about.vbox

        notebook = gtk.Notebook()
        notebook.set_scrollable(True)
        notebook.set_border_width(4)
        notebook.set_tab_pos(gtk.POS_BOTTOM)
        vbox.pack_start(notebook)

        tab_vbox = gtk.VBox()

        def add_label(text, padding_x=0, padding_y=0):
            label = gtk.Label(text)
            label.set_property('use-markup', True)
            label.set_padding(padding_x, padding_y)
            label.set_justify(gtk.JUSTIFY_CENTER)
            tab_vbox.pack_start(label)

        #add_label('<span size="xx-large" weight="bold">Gaphor</span>')
        add_label('<span weight="bold">version %s</span>' % '0.1')
        add_label('<span variant="smallcaps">Simple graphics-oriented python development environment</span>', 8, 8)
        add_label('<span size="small">Copyright (c) 2007-2008 Omar Gomez</span>', 8, 8)
        #vbox.pack_start(gtk.HSeparator())
        notebook.append_page(tab_vbox, gtk.Label('About'))

        tab_vbox = gtk.VBox()
        
        add_label('This software is published\n'
                  'under the terms of the\n'
                  '<span weight="bold">GNU General Public License v2</span>.\n'
                  'See the COPYING file for details.', 0, 8)
        notebook.append_page(tab_vbox, gtk.Label('License'))

        tab_vbox = gtk.VBox()
        
        add_label('PyPreview is written by:\n'
                  'Omar Gomez <omar.gomez@gmail.com>')
        add_label('')
        notebook.append_page(tab_vbox, gtk.Label('Authors'))

        vbox.show_all()
        about.run()
        about.destroy()

    def insert_text(self, buffer):
        global DEF_CODE
        iter = buffer.get_iter_at_offset(0)
        buffer.insert(iter, DEF_CODE)
   
    def buffer_factory(self):
        try:        
            import gtksourceview
            bufferS = gtksourceview.SourceBuffer()
            manager = gtksourceview.SourceLanguagesManager()
            language = manager.get_language_from_mime_type("text/x-python")
            bufferS.set_language(language)
            bufferS.set_highlight(True)
            view = gtksourceview.SourceView(bufferS)
            view.set_show_line_numbers(True)
            return bufferS, view
        except:
            buffer = gtk.TextBuffer()
            return buffer, gtk.TextView(buffer)
    
    def create_text(self):
        bufferS, view = self.buffer_factory()

        scrolled_window = gtk.ScrolledWindow()
        scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scrolled_window.add(view)
        self.insert_text(bufferS)
        scrolled_window.show_all()
        
        self.buffer = bufferS
        
        return ( scrolled_window, bufferS )
    
    def __init__(self):
        # create a new window
        win_width = 800
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_size_request(win_width, 600)
        self.window.set_title("Ogldev")
        self.window.connect("delete_event", lambda w,e: gtk.main_quit())

        ( self.accel_group, self.action_group ) = self.build_actions()

        self.window.add_accel_group( self.accel_group )

        action_menu = gtk.MenuItem("_Actions")
        
        file_menu = gtk.Menu()
        file_menu.show()
        
        self.file_samples = gtk.MenuItem("Examples")        
        file_menu.append( self.file_samples )
        self.file_samples.show()
        
        item = self.action_group.get_action('new_action').create_menu_item()
        item.show()
        file_menu.append( item )
        
        item = self.action_group.get_action('open_action').create_menu_item()
        item.show()
        file_menu.append( item )
        
        item = self.action_group.get_action('save_action').create_menu_item()
        item.show()
        file_menu.append( item )

        item = gtk.MenuItem("Exit")
        item.connect("activate", lambda w,e: gtk.main_quit(), "");
        file_menu.append( item )
        item.show()

        file_item = gtk.MenuItem("_File")
        file_item.set_submenu( file_menu )
        file_item.show()

        action_menu.show()
        
        menu = gtk.Menu()
        item = self.action_group.get_action('refresh_action').create_menu_item()
        item.show()
        menu.append( item )

        action_menu.set_submenu( menu )

        # Now we specify that we want our newly created "menu" to be the
        # menu for the "root menu"
        action_menu.set_submenu(menu)

        # A vbox to put a menu and a button in:
        vbox = gtk.VBox(False, 0)
        self.window.add(vbox)
        vbox.show()

        self.status_bar = gtk.Statusbar()      
        vbox.pack_end(self.status_bar, False, False)
        self.status_bar.show()
        self.context_id = self.status_bar.get_context_id("status_msg")
        self.last_msg_id = None

        # Create a menu-bar to hold the menus and add it to our main window
        menu_bar = gtk.MenuBar()
        vbox.pack_start(menu_bar, False, False, 2)
        menu_bar.show()

        self.hpaned = gtk.HPaned()
        self.hpaned.set_position(win_width/2)
        vbox.pack_end(self.hpaned, True, True, 2)
        self.hpaned.show()
        
        display_mode = (gtk.gdkgl.MODE_RGB    |
                        gtk.gdkgl.MODE_DEPTH  |
                        gtk.gdkgl.MODE_DOUBLE)
        try:
            glconfig = gtk.gdkgl.Config(mode=display_mode)
        except gtk.gdkgl.NoMatches:
            display_mode &= ~gtk.gdkgl.MODE_DOUBLE
            glconfig = gtk.gdkgl.Config(mode=display_mode)

        print "is RGBA:",                 glconfig.is_rgba()
        print "is double-buffered:",      glconfig.is_double_buffered()
        print "is stereo:",               glconfig.is_stereo()
        print "has alpha:",               glconfig.has_alpha()
        print "has depth buffer:",        glconfig.has_depth_buffer()
        print "has stencil buffer:",      glconfig.has_stencil_buffer()
        print "has accumulation buffer:", glconfig.has_accum_buffer()

        entity = EntityDecorator( Entity(), self )
        glarea = GLArea(entity)

        self.hpaned.add2(glarea)

        glarea.show()

        tex_win, self.codeBuffer = self.create_text()
        self.hpaned.add1(tex_win)
        tex_win.show()

        menu_bar.append(file_item)
        menu_bar.append (action_menu)

        # About ....
        help_item = gtk.MenuItem("_Help")
        help_item.show()
        help_menu = gtk.Menu()
        help_menu.show()
        help_item.set_submenu( help_menu )

        item = self.action_group.get_action('about_action').create_menu_item()
        item.show()
        help_menu.append( item )

        menu_bar.append( help_item )
        self.window.show()

        self.fileName = None

    # Print a string when a menu item is selected
    def do_refresh(self, widget):
        self.refresh_canvas()
        
    def ask_file_open(self):
        filew = gtk.FileChooserDialog("Open Script", self.window, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        result = None
        filew.set_default_response(gtk.RESPONSE_OK)
        response = filew.run()
        if response == gtk.RESPONSE_OK:
            result =  filew.get_filename()
        else:
            result = None
        filew.destroy()
        return result	
    
    def get_export_file(self, _filter ):
        filew = gtk.FileChooserDialog("Export to PNG", self.window, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK))
        result = None
        filew.set_default_response(gtk.RESPONSE_OK)
        response = filew.run()
        if response == gtk.RESPONSE_OK:
            result =  filew.get_filename()
        else:
            result = None
        filew.destroy()
        return result
        
    def do_export_png(self, widget):
        file = self.get_export_file('*.png')
        if file == None:
            return
        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 800, 600)
        ctx = cairo.Context(surface)
        try:
            global cairo_draw
            cairo_draw( ctx, 800, 600)
            surface.write_to_png(file)
        except Exception, inst:
            tb = sys.exc_info()[2]
            print_tb(tb)
            tbList = extract_tb(tb)
            lastCall = tbList[len(tbList)-1]
            lineNo = lastCall[1]
            self.set_status_message( 'Line ' + str(lineNo) + ': ' + str(inst) )
            self.move_cursor(lineNo,1)
    
    def runtime_error( self, lineno, offset , msg):
        self.set_status_message( "Runtime error (" + str(msg) + "), at line: " + str(lineno) + ', position: ' + str(offset) )
        self.move_cursor( lineno, offset )
    
    def refresh_canvas( self ):
        try:
            self.clear_status()
            bounds = self.codeBuffer.get_bounds()
            str_code = self.codeBuffer.get_text( bounds[0], bounds[1] )
            str_code = str_code.replace( '\r\n', '\r' )
            compiled = compile( str_code, '<string>', 'exec')
            self.setCode( compiled ) 
            if self.empty_status():
                self.set_status_message( "OK" )
        except exceptions.SyntaxError, inst:
            self.set_status_message( "Syntax error at line: " + str(inst.lineno) + ', position: ' + str(inst.offset) )
            self.move_cursor( inst.lineno, inst.offset )

    def load_examples(self, _dir):
        menu = gtk.Menu()        
        menu.show()
        sample_dir = os.path.join(_dir,"samples-ogl")
        reg = re.compile("^.*\.py$")
        for i in filter( lambda x: reg.match(x.lower()), os.listdir(sample_dir) ):
            # add file name to menu
            menu_item = gtk.MenuItem(i)
            menu_item.connect("activate", self.load_sample, os.path.join( sample_dir, i ) )
            menu_item.show()
            menu.append(menu_item)
            
        self.file_samples.set_submenu(menu)
        
    def load_sample(self, widget, string ):
        #clean buffer
        self.buffer.delete( self.buffer.get_start_iter(), self.buffer.get_end_iter() )
        #add file content ...
        f = open(string)
        self.buffer.insert( self.buffer.get_start_iter(), reduce(lambda n,m:n+m, f.readlines()) )
        f.close() # TODO: try/catch
        self.refresh_canvas()
        self.fileName = None
        self.window.set_title( 'Example' )

    def set_status_message( self, _msg ):
        if self.last_msg_id != None:
            self.status_bar.remove(self.context_id, self.last_msg_id)
        self.last_msg_id = self.status_bar.push( self.context_id, _msg )
        
    def clear_status( self ):
        if self.last_msg_id != None:
            self.status_bar.remove(self.context_id, self.last_msg_id)
            self.last_msg_id = None

    def empty_status( self ):
        return self.last_msg_id == None

    def move_cursor( self, _lineno, _offset ):
        self.buffer.place_cursor( self.buffer.get_iter_at_line_offset(_lineno - 1, _offset - 1) )

    def save(self, widget):
        if self.fileName == None:
            name = self.get_export_file('*.py')
            if name == None:
                return
            self.fileName = name

        script = self.buffer.get_text( self.buffer.get_start_iter(), self.buffer.get_end_iter() )
        f = open(self.fileName, 'w')
        f.write( script )
        f.close() 
        self.window.set_title( self.fileName )

    def open(self, widget):
        name = self.ask_file_open()
        if name == None:
            return
        f = open(name)
        self.buffer.delete( self.buffer.get_start_iter(), self.buffer.get_end_iter() )
        self.buffer.insert( self.buffer.get_start_iter(), reduce(lambda n,m:n+m, f.readlines()) )
        f.close() # TODO: try/catch
        self.refresh_canvas()
        self.fileName = name
        self.window.set_title( self.fileName )

    def new(self, widget):
        self.buffer.delete( self.buffer.get_start_iter(), self.buffer.get_end_iter() )
        self.buffer.insert( self.buffer.get_start_iter(), DEF_CODE )
        self.refresh_canvas()
        self.fileName = None
        self.window.set_title( '<New Script>' )

    def setCode( self, _newCode ):
        self.hpaned.remove(self.hpaned.get_child2())
        exec _newCode in globals()
        entity = EntityDecorator( Entity(), self )
        glarea = GLArea(entity)
        self.hpaned.add2(glarea)        
        glarea.show()

def main():
    gtk.main()
    return 0

if __name__ == "__main__":
    
    start_path = os.path.abspath(os.path.dirname(sys.argv[0]))
    
    win = MainWindow()
    win.load_examples(start_path)
    main()
